// 2025/9/8: 首个版本
// 2025/9/10: 在线程中写文件
// 2025/9/11: 支持全局start_cpu_tick
// 2025/9/12: 不限制队列长度
// 2025/9/15: 无需requireTimeOffset参数

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using ASEva;

namespace CommonSource
{
    class DataTracker
    {
        public static void SetStartCPUTick(string frameID, ulong startCPUTick)
        {
            ulong cpuTick = Agency.GetCPUTick();
            if (startCPUTick > cpuTick) return;
            writeFileThread.Add(new WriteFileTask(frameID, startCPUTick));
        }

        public static void Mark(string frameID, string location)
        {
            ulong cpuTick = Agency.GetCPUTick();
            writeFileThread.Add(new WriteFileTask(frameID, location, cpuTick));
        }

        public static void Terminate()
        {
            writeFileThread.StopThread();
        }

        private static string ensureRootFolder()
        {
            string desktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Desktop");
            string rootFolder = Path.Combine(desktopPath, LogRootFolderName);
            if (!Directory.Exists(rootFolder))
            {
                try { Directory.CreateDirectory(rootFolder); }
                catch { return null; }
            }
            return rootFolder;
        }

        private static string ensureFrameFolder(string frameID)
        {
            var rootFolder = ensureRootFolder();
            if (rootFolder == null) return null;

            string frameFolder = Path.Combine(rootFolder, frameID);
            if (!Directory.Exists(frameFolder))
            {
                try { Directory.CreateDirectory(frameFolder); }
                catch { return null; }
            }
            return frameFolder;
        }

        private class WriteFileTask
        {
            public bool IsStartCPUTick { get; set; }
            public string FrameID { get; set; }
            public ulong StartCPUTick { get; set; }
            public string Location { get; set; }
            public ulong CPUTick { get; set; }
            public WriteFileTask(string frameID, ulong startCPUTick)
            {
                IsStartCPUTick = true;
                FrameID = frameID;
                StartCPUTick = startCPUTick;
            }
            public WriteFileTask(string frameID, string location, ulong cpuTick)
            {
                IsStartCPUTick = false;
                FrameID = frameID;
                Location = location;
                CPUTick = cpuTick;
            }
        }

        private class WriteFileThread
        {
            public void Add(WriteFileTask task)
            {
                lock (this)
                {
                    if (shouldEnd) return;
                    if (thread == null)
                    {
                        thread = new Thread(workThreadFunc);
                        thread.Start();
                    }
                }
                writeFileTasks.Enqueue(task);
            }

            public void StopThread()
            {
                lock (this)
                {
                    if (shouldEnd) return;
                    shouldEnd = true;
                }
                if (thread != null) thread.Join();
            }

            private void workThreadFunc()
            {
                while (!shouldEnd)
                {
                    Thread.Sleep(1);

                    WriteFileTask task;
                    if (!writeFileTasks.TryDequeue(out task)) continue;

                    string rootFolder = ensureRootFolder();
                    string frameFolder = ensureFrameFolder(task.FrameID);
                    if (frameFolder == null) return;

                    if (task.IsStartCPUTick)
                    {
                        try
                        {
                            foreach (string file in Directory.GetFiles(frameFolder))
                            {
                                File.Delete(file);
                            }

                            string startCPUTickPath = Path.Combine(frameFolder, StartCPUTickFileName);
                            File.WriteAllText(startCPUTickPath, task.StartCPUTick.ToString());
                        }
                        catch { }
                    }
                    else
                    {
                        string globalStartCPUTickPath = Path.Combine(rootFolder, StartCPUTickFileName);
                        string localStartCPUTickPath = Path.Combine(frameFolder, StartCPUTickFileName);
                        string targetStartCPUTickPath = null;
                        if (File.Exists(localStartCPUTickPath)) targetStartCPUTickPath = localStartCPUTickPath;
                        else if (File.Exists(globalStartCPUTickPath)) targetStartCPUTickPath = globalStartCPUTickPath;

                        ulong startCPUTick = 0;
                        if (targetStartCPUTickPath != null)
                        {
                            try
                            {
                                string content = File.ReadAllText(targetStartCPUTickPath);
                                if (ulong.TryParse(content, out ulong parsedValue) && parsedValue > 0)
                                {
                                    startCPUTick = parsedValue;
                                }
                            }
                            catch { }
                        }

                        string fileName;
                        if (startCPUTick != 0)
                        {
                            var cpuTicksPerSecond = Agency.GetCPUTicksPerSecond();
                            double timeOffset = (double)(task.CPUTick - startCPUTick) / cpuTicksPerSecond;
                            uint timeOffsetSecond = (uint)Math.Floor(timeOffset);
                            uint timeOffsetUS = (uint)Math.Floor((timeOffset - timeOffsetSecond) * 1000000);
                            fileName = timeOffsetSecond.ToString("D5") + "_" + timeOffsetUS.ToString("D6") + "_" + task.Location;
                        }
                        else
                        {
                            fileName = task.CPUTick.ToString() + "_" + task.Location;
                        }

                        try
                        {
                            string filePath = Path.Combine(frameFolder, fileName);
                            File.Create(filePath).Close();
                        }
                        catch { }
                    }
                }
            }

            private bool shouldEnd = false;
            private Thread thread = null;
            private ConcurrentQueue<WriteFileTask> writeFileTasks = new ConcurrentQueue<WriteFileTask>();
        }

        private static WriteFileThread writeFileThread = new WriteFileThread();

        private const string LogRootFolderName = "DataTracker";
        private const string StartCPUTickFileName = "start_cpu_tick.txt";
    }
}